/*
 * Copyright (c) 2007, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces;

import static com.nulldev.util.graphics.renderIt.ginterfaces.marlin.impl.pisces.MarlinUtils.logInfo;

import java.security.AccessController;
import java.security.PrivilegedAction;

import sun.security.action.GetPropertyAction;

public final class MarlinProperties {

	private static Boolean SUPPORT_LARGE_TILES = null;

	private MarlinProperties() {
		// no-op
	}

	// Specific AWT / Java2D Graphics Environment checks

	private static boolean supportsLargeTiles() {
		if (SUPPORT_LARGE_TILES != null) {
			return SUPPORT_LARGE_TILES.booleanValue();
		}
		boolean useLargeTiles = false;
		try {
			// Due to bootstrapping issue with GraphicsEnvironment static initializers,
			// Try detecting the awt / java2d pipeline in action using only
			// system & env properties:
			if (isHeadless()) {
				useLargeTiles = true;
			} else {
				final String osName = getString("os.name", "");
				// Ignore Windows / Mac, only Linux:
				if ("Linux".equals(osName)) {
					final boolean useGL = getBoolean("sun.java2d.opengl", "false");
					final boolean useXR = getBoolean("sun.java2d.xrender", "true");
					if (!useGL && useXR) {
						// !OpenGL AND XRender on Linux:
						useLargeTiles = true;
					}
				}
			}
		} finally {
			SUPPORT_LARGE_TILES = Boolean.valueOf(useLargeTiles);
		}
		return useLargeTiles;
	}

	private static boolean isHeadless() {
		// Mimics java.awt.GraphicsEnvironment.getHeadlessProperty():
		return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
			@Override
			public Boolean run() {
				String nm = System.getProperty("java.awt.headless");

				if (nm == null) {
					String osName = System.getProperty("os.name");
					if (osName.contains("OS X") && "sun.awt.HToolkit".equals(System.getProperty("awt.toolkit"))) {
						return Boolean.TRUE;
					} else {
						final String display = System.getenv("DISPLAY");
						return Boolean.valueOf(("Linux".equals(osName) || "SunOS".equals(osName) || "FreeBSD".equals(osName) || "NetBSD".equals(osName)
								|| "OpenBSD".equals(osName) || "AIX".equals(osName)) && (display == null || display.trim().isEmpty()));
					}
				} else {
					return Boolean.valueOf(nm);
				}
			}
		});
	}

	// marlin system properties

	public static boolean isUseThreadLocal() {
		return getBoolean("sun.java2d.renderer.useThreadLocal", "true");
	}

	/**
	 * Return the initial edge capacity used to define initial arrays (edges,
	 * polystack, crossings)
	 *
	 * @return 256 < initial edges < 65536 (4096 by default)
	 */
	public static int getInitialEdges() {
		return align(getInteger("sun.java2d.renderer.edges", 4096, 64, 64 * 1024), 64);
	}

	/**
	 * Return the initial pixel width used to define initial arrays (tile AA chunk,
	 * alpha line)
	 *
	 * @return 64 < initial pixel size < 32768 (4096 by default)
	 */
	public static int getInitialPixelWidth() {
		return align(getInteger("sun.java2d.renderer.pixelWidth", 4096, 64, 32 * 1024), 64);
	}

	/**
	 * Return the initial pixel height used to define initial arrays (buckets)
	 *
	 * @return 64 < initial pixel size < 32768 (2176 by default)
	 */
	public static int getInitialPixelHeight() {
		return align(getInteger("sun.java2d.renderer.pixelHeight", 2176, 64, 32 * 1024), 64);
	}

	/**
	 * Return true if the profile is 'quality' (default) over 'speed'
	 *
	 * @return true if the profile is 'quality' (default), false otherwise
	 */
	public static boolean isProfileQuality() {
		final String key = "sun.java2d.renderer.profile";
		final String profile = getString(key, "quality");
		if ("quality".equals(profile)) {
			return true;
		}
		if ("speed".equals(profile)) {
			return false;
		}
		logInfo("Invalid value for " + key + " = " + profile + "; expect value in [quality, speed] !");
		return true;
	}

	/**
	 * Return the log(2) corresponding to subpixel on x-axis
	 *
	 * @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels) (8 by
	 *         default ie 256 subpixels)
	 */
	public static int getSubPixel_Log2_X() {
		return getInteger("sun.java2d.renderer.subPixel_log2_X", 8, 0, 8);
	}

	/**
	 * Return the log(2) corresponding to subpixel on y-axis
	 *
	 * @return 0 (1 subpixels) < initial pixel size < 8 (256 subpixels) (3 by
	 *         default ie 8 subpixels for the quality profile) (2 by default ie 4
	 *         subpixels for the speed profile)
	 */
	public static int getSubPixel_Log2_Y() {
		final int def = isProfileQuality() ? 3 : 2;
		return getInteger("sun.java2d.renderer.subPixel_log2_Y", def, 0, 8);
	}

	/**
	 * Return the log(2) corresponding to the square tile size in pixels
	 *
	 * @return 3 (8x8 pixels) < tile size < 10 (1024x1024 pixels) (6 by default ie
	 *         128x64 pixels if large tile supported) (5 by default ie 32x32 pixels
	 *         otherwise)
	 */
	public static int getTileSize_Log2() {
		final int def = supportsLargeTiles() ? 6 : 5;
		return getInteger("sun.java2d.renderer.tileSize_log2", def, 3, 10);
	}

	/**
	 * Return the log(2) corresponding to the tile width in pixels
	 *
	 * @return 3 (8 pixels) < tile width < 10 (1024 pixels) (7 by default ie 128x64
	 *         pixels if large tile supported) (5 by default ie 32x32 pixels
	 *         otherwise)
	 */
	public static int getTileWidth_Log2() {
		final int def = supportsLargeTiles() ? 7 : 5;
		return getInteger("sun.java2d.renderer.tileWidth_log2", def, 3, 10);
	}

	/**
	 * Return the log(2) corresponding to the block size in pixels
	 *
	 * @return 3 (8 pixels) < block size < 8 (256 pixels) (5 by default ie 32
	 *         pixels)
	 */
	public static int getBlockSize_Log2() {
		return getInteger("sun.java2d.renderer.blockSize_log2", 5, 3, 8);
	}

	// RLE / blockFlags settings

	public static boolean isForceRLE() {
		return getBoolean("sun.java2d.renderer.forceRLE", "false");
	}

	public static boolean isForceNoRLE() {
		return getBoolean("sun.java2d.renderer.forceNoRLE", "false");
	}

	public static boolean isUseTileFlags() {
		return getBoolean("sun.java2d.renderer.useTileFlags", "true");
	}

	public static boolean isUseTileFlagsWithHeuristics() {
		return isUseTileFlags() && getBoolean("sun.java2d.renderer.useTileFlags.useHeuristics", "true");
	}

	public static int getRLEMinWidth() {
		return getInteger("sun.java2d.renderer.rleMinWidth", 64, 0, Integer.MAX_VALUE);
	}

	// optimisation parameters

	public static boolean isUseSimplifier() {
		return getBoolean("sun.java2d.renderer.useSimplifier", "false");
	}

	public static boolean isUsePathSimplifier() {
		return getBoolean("sun.java2d.renderer.usePathSimplifier", "false");
	}

	public static float getPathSimplifierPixelTolerance() {
		// default: MIN_PEN_SIZE or less ?
		return getFloat("sun.java2d.renderer.pathSimplifier.pixTol", (1.0f / MarlinConst.MIN_SUBPIXELS), 1e-3f, 10.0f);
	}

	public static boolean isDoClip() {
		return getBoolean("sun.java2d.renderer.clip", "true");
	}

	public static boolean isDoClipRuntimeFlag() {
		return getBoolean("sun.java2d.renderer.clip.runtime.enable", "false");
	}

	public static boolean isDoClipAtRuntime() {
		return getBoolean("sun.java2d.renderer.clip.runtime", "true");
	}

	public static boolean isDoClipSubdivider() {
		return getBoolean("sun.java2d.renderer.clip.subdivider", "true");
	}

	public static float getSubdividerMinLength() {
		return getFloat("sun.java2d.renderer.clip.subdivider.minLength", 100.0f, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
	}

	public static boolean isUseDPQS() {
		return getBoolean("sun.java2d.renderer.useDPQS", "true");
	}

	// debugging parameters

	public static boolean isDoStats() {
		return getBoolean("sun.java2d.renderer.doStats", "false");
	}

	public static boolean isDoMonitors() {
		return getBoolean("sun.java2d.renderer.doMonitors", "false");
	}

	public static boolean isDoChecks() {
		return getBoolean("sun.java2d.renderer.doChecks", "false");
	}

	// logging parameters

	public static boolean isLoggingEnabled() {
		return getBoolean("sun.java2d.renderer.log", "true");
	}

	public static boolean isUseLogger() {
		return getBoolean("sun.java2d.renderer.useLogger", "false");
	}

	public static boolean isLogCreateContext() {
		return getBoolean("sun.java2d.renderer.logCreateContext", "false");
	}

	public static boolean isLogUnsafeMalloc() {
		return getBoolean("sun.java2d.renderer.logUnsafeMalloc", "false");
	}

	// quality settings
	public static float getCurveLengthError() {
		return getFloat("sun.java2d.renderer.curve_len_err", 0.01f, 1e-6f, 1.0f);
	}

	public static float getCubicDecD2() {
		final float def = isProfileQuality() ? 1.0f : 2.5f;
		return getFloat("sun.java2d.renderer.cubic_dec_d2", def, 1e-5f, 4.0f);
	}

	public static float getCubicIncD1() {
		final float def = isProfileQuality() ? 0.2f : 0.5f;
		return getFloat("sun.java2d.renderer.cubic_inc_d1", def, 1e-6f, 1.0f);
	}

	public static float getQuadDecD2() {
		final float def = isProfileQuality() ? 0.5f : 1.0f;
		return getFloat("sun.java2d.renderer.quad_dec_d2", def, 1e-5f, 4.0f);
	}

	// system property utilities
	static String getString(final String key, final String def) {
		return AccessController.doPrivileged(new GetPropertyAction(key, def));
	}

	static boolean getBoolean(final String key, final String def) {
		return Boolean.valueOf(AccessController.doPrivileged(new GetPropertyAction(key, def)));
	}

	static int getInteger(final String key, final int def, final int min, final int max) {
		final String property = AccessController.doPrivileged(new GetPropertyAction(key));

		int value = def;
		if (property != null) {
			try {
				value = Integer.decode(property);
			} catch (NumberFormatException e) {
				logInfo("Invalid integer value for " + key + " = " + property);
			}
		}

		// check for invalid values
		if ((value < min) || (value > max)) {
			logInfo("Invalid value for " + key + " = " + value + "; expected value in range[" + min + ", " + max + "] !");
			value = def;
		}
		return value;
	}

	static int align(final int val, final int norm) {
		final int ceil = FloatMath.ceil_int(((float) val) / norm);
		return ceil * norm;
	}

	public static double getDouble(final String key, final double def, final double min, final double max) {
		double value = def;
		final String property = AccessController.doPrivileged(new GetPropertyAction(key));

		if (property != null) {
			try {
				value = Double.parseDouble(property);
			} catch (NumberFormatException nfe) {
				logInfo("Invalid value for " + key + " = " + property + " !");
			}
		}
		// check for invalid values
		if (value < min || value > max) {
			logInfo("Invalid value for " + key + " = " + value + "; expect value in range[" + min + ", " + max + "] !");
			value = def;
		}
		return value;
	}

	public static float getFloat(final String key, final float def, final float min, final float max) {
		return (float) getDouble(key, def, min, max);
	}
}
